﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Web;

namespace EmaMetadata.Serializers
{
    public class Version4 : BaseSerializer
    {
        #region Loading
        public override EmaBatch Load(XmlDocument d)
        {
            EmaBatch b = new EmaBatch();

            // First, what do we know about the root element of this file?  Is it an "EntertainmentProducts" node?
            if (d.DocumentElement.Name == "EntertainmentProducts") {
                b.RecordVersion = d.DocumentElement.ValueOrDefault("RecordVersion");
                b.LastUpdateDate = d.DocumentElement.ValueOrDefaultDateTime("LastUpdateDate");
                b.DefaultDistanceUOM = d.DocumentElement.ValueOrDefault("default_distance_uom");
                b.DefaultMassUOM = d.DocumentElement.ValueOrDefault("default_mass_uom");

                // Read in the metadata creator information
                XmlNode n = d.DocumentElement.SelectSingleNode("MetadataCreator/Name");
                if (n != null) {
                    b.MetadataCreatorName = n.InnerText;
                }
                n = d.DocumentElement.SelectSingleNode("MetadataCreator/GLN");
                if (n != null) {
                    b.MetadataCreatorGLN = n.InnerText;
                }
                n = d.DocumentElement.SelectSingleNode("MetadataCreator/FQDN");
                if (n != null) {
                    b.MetadataCreatorFQDN = n.InnerText;
                }

                // Go through each product node and add it
                foreach (XmlNode child in d.DocumentElement.SelectNodes("Product")) {
                    EmaProduct p = ReadProduct(child);
                    b.Products.Add(p);
                }

                // No extra information - this is just a plain list
            } else if (d.DocumentElement.Name == "Products") {

                // Go through each product node and add it
                foreach (XmlNode n in d.DocumentElement.ChildNodes) {
                    EmaProduct p = ReadProduct(n);
                    b.Products.Add(p);
                }
            }
            return b;
        }
        #endregion

        #region Saving
        public override void Save(XmlWriter xw, EmaBatch b)
        {
            // Make the main element
            xw.WriteStartElement("EntertainmentProducts");

            // This serializer always writes version "4"
            xw.WriteAttributeString("RecordVersion", "4");

            // Values provided by creator
            xw.WriteAttributeString("LastUpdateDate", b.LastUpdateDate.HasValue ? b.LastUpdateDate.Value.ToUniversalTime().ToString() : "");
            xw.WriteAttributeString("default_distance_uom", b.DefaultDistanceUOM);
            xw.WriteAttributeString("default_mass_uom", b.DefaultMassUOM);

            // Make the metadata creator element
            xw.WriteStartElement("MetadataCreator");
            xw.WriteElementString("Name", b.MetadataCreatorName);
            xw.WriteElementString("GLN", b.MetadataCreatorGLN);
            xw.WriteElementString("FQDN", b.MetadataCreatorFQDN);
            xw.WriteEndElement();

            // Add each product to it
            foreach (EmaProduct p in b.Products) {
                WriteProduct(xw, p);
            }

            // Write the end of the main entertainment products file
            xw.WriteEndElement();
        }
        #endregion

        #region Saving to Xml
        /// <summary>
        /// Serialize information in this document to XML
        /// </summary>
        /// <param name="d"></param>
        public override void WriteProduct(XmlWriter xw, EmaProduct p)
        {
            xw.WriteStartElement("Product");

            // Write the identity section
            xw.WriteStartElement("Identity");
            foreach (EmaIdentity i in p.Identities) {
                SaveIdentityToXml(xw, i);
            }
            xw.WriteEndElement();

            // Write the text section
            xw.WriteStartElement("Text");
            foreach (EmaTitle t in p.Titles) {
                SaveTitleToXml(xw, t);
            }
            foreach (EmaDescription d in p.Descriptions) {
                SaveDescriptionToXml(xw, d);
            }
            xw.WriteEndElement();

            // Write the descriptors section
            xw.WriteStartElement("Descriptors");
            SaveDescriptorsToXml(xw, p);
            xw.WriteEndElement();

            // Write the characteristics section
            xw.WriteStartElement("Characteristics");
            if (p.Characteristics != null) {
                SaveFactAsAttributes(xw, p.Characteristics.CharacteristicsData);
                xw.WriteStartElement("Vendor");
                SaveFactAsAttributes(xw, p.Characteristics.VendorData);
                xw.WriteEndElement();
                xw.WriteStartElement("DownloadSize");
                SaveFactAsAttributes(xw, p.Characteristics.DownloadSizeData);
                xw.WriteValue(p.Characteristics.Size.ToString());
                xw.WriteEndElement();
            }
            xw.WriteEndElement();

            // Write the ratings section
            xw.WriteStartElement("Ratings");
            foreach (EmaRating r in p.Ratings) {
                SaveRatingToXml(xw, r);
            }
            xw.WriteEndElement();

            // Write the extra section
            if (!String.IsNullOrEmpty(p.Extra)) {
                xw.WriteStartElement("Extra");
                xw.WriteRaw(p.Extra);
                xw.WriteEndElement();
            }

            // Close out the entire product
            xw.WriteEndElement();
        }

        private static void SaveDescriptorsToXml(XmlWriter xw, EmaProduct p)
        {
            // Write companies
            xw.WriteStartElement("Companies");
            xw.WriteAttributeString("PrimaryPublisher", p.PrimaryPublisher);
            xw.WriteAttributeString("PrimaryDeveloper", p.PrimaryDeveloper);
            foreach (EmaPublisher c in p.Publishers) {
                xw.WriteStartElement("Publisher");
                xw.WriteAttributeString("rel", c.Relevance.ToString());
                xw.WriteValue(c.Name);
                xw.WriteEndElement();
            }
            foreach (EmaDeveloper c in p.Developers) {
                xw.WriteStartElement("Developer");
                xw.WriteAttributeString("rel", c.Relevance.ToString());
                xw.WriteValue(c.Value);
                xw.WriteEndElement();
            }
            xw.WriteEndElement();

            // Write regions
            xw.WriteStartElement("Regions");
            foreach (EmaRegion r in p.Regions) {
                xw.WriteStartElement("Region");
                xw.WriteAttributeString("code", r.CountryCode);
                if (r.ReleaseDate.HasValue) {
                    xw.WriteAttributeString("ReleaseDate", r.ReleaseDate.Value.ToString("yyyy-MM-dd"));
                }
                xw.WriteEndElement();
            }
            xw.WriteEndElement();

            // Write a few facts: Genre, EditionType, PrimaryProductLine, Platform
            if (p.Genre != null) SaveFactAsElement(xw, "Genre", p.Genre);
            if (p.EditionType != null) SaveFactAsElement(xw, "EditionType", p.EditionType);
            if (p.PrimaryProductLine != null) SaveFactAsElement(xw, "PrimaryProductLine", p.PrimaryProductLine);
            if (p.Platform != null) SaveFactAsElement(xw, "Platform", p.Platform);

            // Write players
            xw.WriteStartElement("Players");
            xw.WriteAttributeString("minimum", p.MinPlayers.ToString());
            xw.WriteAttributeString("maximum", p.MaxPlayers.ToString());
            foreach (EmaGameplay g in p.Players) {
                xw.WriteStartElement("Gameplay");
                SaveFactAsAttributes(xw, g.GameplayData);
                if (g.Min > 0) {
                    xw.WriteAttributeString("minimum", g.Min.ToString());
                }
                if (g.Max > 0) {
                    xw.WriteAttributeString("maximum", g.Max.ToString());
                }
                xw.WriteEndElement();
            }
            xw.WriteEndElement();

            // Write images
            xw.WriteStartElement("Images");
            SaveFactAsAttributes(xw, p.ImageStorageType);
            xw.WriteAttributeString("base", p.ImageStorageBase);
            foreach (EmaImage i in p.Images) {
                xw.WriteStartElement("Image");
                SaveFactAsAttributes(xw, i.ImageData);
                xw.WriteAttributeString("alt", i.Alt);
                xw.WriteAttributeString("width", i.Width.ToString());
                xw.WriteAttributeString("height", i.Height.ToString());
                xw.WriteValue(i.Location);
                xw.WriteEndElement();
            }
            xw.WriteEndElement();

            // Write pricing
            xw.WriteStartElement("Pricing");
            foreach (EmaPrice pr in p.Pricing) {
                xw.WriteStartElement("Price");
                SaveFactAsAttributes(xw, pr.PriceData);
                xw.WriteAttributeString("region", pr.CountryCode);
                xw.WriteAttributeString("currency", pr.Currency);
                if (pr.EffectiveDate.HasValue) {
                    xw.WriteAttributeString("effective_date", pr.EffectiveDate.Value.ToString("yyyy-MM-dd"));
                }
                xw.WriteValue(pr.Value.ToString());
                xw.WriteEndElement();
            }
            xw.WriteEndElement();
        }

        private static void SaveFactAsElement(XmlWriter xw, string name, EmaFact f)
        {
            xw.WriteStartElement(name);
            if (f.NS != Factory.ENTMERCH_NAMESPACE) {
                xw.WriteAttributeString("ns", f.NS);
            }
            xw.WriteAttributeString("id", f.ID.ToString());
            xw.WriteValue(f.FactName);
            xw.WriteEndElement();
        }

        private static void SaveDescriptionToXml(XmlWriter xw, EmaDescription d)
        {
            xw.WriteStartElement("Description");
            SaveFactAsAttributes(xw, d.DescriptionData);
            xw.WriteValue(XmlFixup(d.Value));
            xw.WriteEndElement();
        }

        private static void SaveTitleToXml(XmlWriter xw, EmaTitle t)
        {
            xw.WriteStartElement("Title");
            SaveFactAsAttributes(xw, t.TitleData);
            xw.WriteValue(t.Value);
            xw.WriteEndElement();
        }

        private static void SaveIdentityToXml(XmlWriter xw, EmaIdentity i)
        {
            xw.WriteStartElement("ID");
            xw.WriteAttributeString("ns", i.IdentityData.NS);
            xw.WriteAttributeString("type", i.IdentityData.FactName);
            xw.WriteAttributeString("id", i.IdentityData.ID.ToString());
            xw.WriteValue(i.Value);
            xw.WriteEndElement();
        }

        /// <summary>
        /// Serialize a ratings object
        /// </summary>
        /// <param name="xw"></param>
        /// <param name="r"></param>
        private static void SaveRatingToXml(XmlWriter xw, EmaRating r)
        {
            xw.WriteStartElement("Agency");
            SaveFactAsAttributes(xw, r.AgencyData);
            xw.WriteAttributeString("Certificate", r.Certificate);
            xw.WriteAttributeString("rating", r.Rating);
            foreach (string s in r.Descriptors) {
                xw.WriteElementString("Descriptor", s);
            }
            xw.WriteEndElement();
        }

        /// <summary>
        /// Serialize information in this document to XML, as attributes
        /// </summary>
        /// <param name="d"></param>
        private static void SaveFactAsAttributes(XmlWriter xw, EmaFact f)
        {
            if (f != null) {

                // Only save namespace if not the default
                if (f.NS != Factory.ENTMERCH_NAMESPACE) {
                    xw.WriteAttributeString("ns", f.NS);
                }
                xw.WriteAttributeString("id", f.ID.ToString());
                xw.WriteAttributeString("type", f.FactName);
            }
        }
        #endregion

        #region Loading From Xml
        /// <summary>
        /// Load one single product by an XmlNode
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public override EmaProduct ReadProduct(XmlNode n)
        {
            EmaProduct p = new EmaProduct();

            // Read all identities under the "Identity" node
            foreach (XmlNode c in n.SelectNodes("Identity/ID")) {
                EmaIdentity id = GetIdentity(c);
                p.Identities.Add(id);
            }

            // Handle titles
            foreach (XmlNode c in n.SelectNodes("Text/Title")) {
                EmaTitle t = GetTitle(c);
                p.Titles.Add(t);
            }

            // Handle descriptions
            foreach (XmlNode c in n.SelectNodes("Text/Description")) {
                EmaDescription d = GetDescription(c);
                p.Descriptions.Add(d);
            }

            // Primary pub and dev
            XmlNode companies = n.SelectSingleNode("Descriptors/Companies");
            if (companies != null) {
                p.PrimaryPublisher = companies.ValueOrDefault("PrimaryPublisher");
                p.PrimaryDeveloper = companies.ValueOrDefault("PrimaryDeveloper");

                // Handle the full list of companies
                foreach (XmlNode c in n.SelectNodes("Descriptors/Companies/*")) {
                    if (c.Name == "Publisher") {
                        EmaPublisher pub = GetPublisher(c);
                        p.Publishers.Add(pub);
                    } else if (c.Name == "Developer") {
                        EmaDeveloper dev = GetDeveloper(c);
                        p.Developers.Add(dev);
                    }
                }
            }

            // Handle regions
            foreach (XmlNode c in n.SelectNodes("Descriptors/Regions/Region")) {
                EmaRegion r = GetRegion(c);
                p.Regions.Add(r);
            }

            // Handle genre, edition type, primary product line, and platform
            p.Genre = GetFactElement(n.SelectSingleNode("Descriptors/Genre"));
            p.EditionType = GetFactElement(n.SelectSingleNode("Descriptors/EditionType"));
            p.PrimaryProductLine = GetFactElement(n.SelectSingleNode("Descriptors/PrimaryProductLine"));
            p.Platform = GetFactElement(n.SelectSingleNode("Descriptors/Platform"));

            // Handle "player count" information
            XmlNode players = n.SelectSingleNode("Descriptors/Players");
            if (players != null) {
                p.MinPlayers = players.ValueOrDefaultInt("minimum");
                p.MaxPlayers = players.ValueOrDefaultInt("maximum");
                foreach (XmlNode c in players.SelectNodes("Gameplay")) {
                    EmaGameplay g = GetGameplay(c);
                    p.Players.Add(g);
                }
            }

            // Handle image data
            XmlNode images = n.SelectSingleNode("Descriptors/Images");
            if (images != null) {
                p.ImageStorageBase = images.ValueOrDefault("base");
                p.ImageStorageType = GetFact(images);
                foreach (XmlNode c in images.SelectNodes("Image")) {
                    EmaImage i = GetImage(c);
                    p.Images.Add(i);
                }
            }

            // Handle pricing
            foreach (XmlNode c in n.SelectNodes("Descriptors/Pricing/Price")) {
                EmaPrice price = GetPrice(c);
                p.Pricing.Add(price);
            }

            // Handle characteristics
            XmlNode characteristics = n.SelectSingleNode("Characteristics");
            if (characteristics != null) {
                p.Characteristics = GetCharacteristics(characteristics);
            }

            // Handle ratings
            foreach (XmlNode c in n.SelectNodes("Ratings/Agency")) {
                EmaRating r = GetRating(c);
                p.Ratings.Add(r);
            }

            // All the extra data goes here
            XmlNode extra = n.SelectSingleNode("Extra");
            if (extra != null) {
                p.Extra = extra.InnerXml;
            }

            // Here's the product!
            return p;
        }

        private static EmaPrice GetPrice(XmlNode c)
        {
            EmaPrice p = new EmaPrice();
            p.PriceData = GetFact(c);
            p.CountryCode = c.ValueOrDefault("region");
            p.Currency = c.ValueOrDefault("currency");
            p.EffectiveDate = c.ValueOrDefaultDateTime("effective_date");
            Decimal.TryParse(c.InnerText, out p.Value);
            return p;
        }

        private static EmaImage GetImage(XmlNode c)
        {
            EmaImage i = new EmaImage();
            i.ImageData = GetFact(c);
            i.Alt = c.ValueOrDefault("alt");
            i.Width = c.ValueOrDefaultInt("width");
            i.Height = c.ValueOrDefaultInt("height");
            i.Location = c.InnerText;
            return i;
        }

        private static EmaGameplay GetGameplay(XmlNode c)
        {
            EmaGameplay g = new EmaGameplay();
            g.GameplayData = GetFact(c);
            g.Max = c.ValueOrDefaultInt("maximum");
            g.Min = c.ValueOrDefaultInt("minimum");
            return g;
        }

        private static EmaRegion GetRegion(XmlNode c)
        {
            EmaRegion r = new EmaRegion();
            r.CountryCode = c.ValueOrDefault("code");
            r.ReleaseDate = c.ValueOrDefaultDateTime("ReleaseDate");
            return r;
        }

        private static EmaDeveloper GetDeveloper(XmlNode c)
        {
            EmaDeveloper d = new EmaDeveloper();
            d.Relevance = c.ValueOrDefaultInt("rel");
            d.Value = c.InnerText;
            return d;
        }

        private static EmaPublisher GetPublisher(XmlNode c)
        {
            EmaPublisher p = new EmaPublisher();
            p.Relevance = c.ValueOrDefaultInt("rel");
            p.Name = c.InnerText;
            return p;
        }

        private static EmaCharacteristics GetCharacteristics(XmlNode n)
        {
            EmaCharacteristics c = new EmaCharacteristics();
            c.CharacteristicsData = GetFact(n);

            // Retrieve vendor data
            XmlNode vendor_node = n.SelectSingleNode("Vendor");
            if (vendor_node != null) {
                c.VendorData = GetFact(vendor_node);
            }

            // Retrieve download size data
            XmlNode download_size_node = n.SelectSingleNode("DownloadSize");
            if (download_size_node != null) {
                c.DownloadSizeData = GetFact(download_size_node);
                Decimal.TryParse(download_size_node.InnerText, out c.Size);
            }

            return c;
        }

        private static EmaRating GetRating(XmlNode c)
        {
            EmaRating r = new EmaRating();
            r.AgencyData = GetFact(c);
            r.Certificate = c.ValueOrDefault("Certificate");
            r.Rating = c.ValueOrDefault("Rating");

            // Handle descriptors
            foreach (XmlNode n in c.SelectNodes("Descriptor")) {
                r.Descriptors.Add(n.InnerText);
            }

            // Done
            return r;
        }

        private static EmaDescription GetDescription(XmlNode c)
        {
            EmaDescription d = new EmaDescription();
            d.DescriptionData = GetFact(c);
            d.Value = c.InnerText;
            return d;
        }

        private static EmaTitle GetTitle(XmlNode c)
        {
            EmaTitle t = new EmaTitle();
            t.TitleData = GetFact(c);
            t.Value = c.InnerText;
            return t;
        }

        private static EmaIdentity GetIdentity(XmlNode n)
        {
            EmaIdentity i = new EmaIdentity();
            i.IdentityData = GetFact(n);
            i.Value = n.InnerText;
            return i;
        }

        private static EmaFact GetFactElement(XmlNode n)
        {
            if (n == null) return null;
            EmaFact f = new EmaFact();
            f.ID = n.ValueOrDefaultInt("ID");
            f.NS = n.ValueOrDefault("NS");
            f.FactName = n.InnerText;
            if (String.IsNullOrEmpty(f.NS)) {
                f.NS = Factory.ENTMERCH_NAMESPACE;
            }
            return f;
        }

        private static EmaFact GetFact(XmlNode n)
        {
            EmaFact f = new EmaFact();
            f.ID = n.ValueOrDefaultInt("ID");
            f.NS = n.ValueOrDefault("NS");
            f.FactName = n.ValueOrDefault("Type");
            if (String.IsNullOrEmpty(f.NS)) {
                f.NS = Factory.ENTMERCH_NAMESPACE;
            }
            return f;
        }
        #endregion
    }
}
